﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Xml.Serialization;
using System.IO;

namespace DELVE_GAME
{
    #region map list

    [Serializable]
    public class MapManager
    {
        const string mapDirectory = "Content/Maps";
        const string managerFile = "mapList.xml";

        public List<MapTag> maps;
        int selectedMap;

        public MapManager()
        {
            maps = new List<MapTag>();
            selectedMap = -1;
        }

        public int getNumMaps() { return maps.Count; }
        public void selectMap(int select) { selectedMap = select; }

        public void readFromFile()
        {
            string fileLoc = mapDirectory + "/" + managerFile;
            if (File.Exists(fileLoc))
            {
                MapManager newList = new MapManager();
                XmlSerializer serializer = new XmlSerializer(newList.GetType());
                FileStream xmlFileStream = File.Open(fileLoc, FileMode.Open);
                newList = (MapManager)serializer.Deserialize(xmlFileStream);
                xmlFileStream.Close();
                this.maps = newList.maps;
            }
            else writeToFile();
        }

        public void writeToFile()
        {
            string fileLoc = mapDirectory + "/" + managerFile;
            if (!Directory.Exists(mapDirectory)) Directory.CreateDirectory(mapDirectory);
            XmlSerializer serializer = new XmlSerializer(this.GetType());
            StreamWriter writer = new StreamWriter(fileLoc);
            serializer.Serialize(writer, this);
            writer.Close();
        }

        //JONATHAN!!!
        public void addMap(Map toAdd)
        {
            MapTag newTag = new MapTag();
            string mapLocation = toAdd.name + ".xml";
            newTag.fileName = mapLocation;
            newTag.width = toAdd.board.Length;
            newTag.height = toAdd.board[0].Length;
            newTag.hasTerritories = (toAdd.territories.Count > 0);
            newTag.hasRaceTerritory = (toAdd.raceTerritory.Count > 0);
            newTag.numPlayers = toAdd.startingAreas.Count;

            mapLocation = mapDirectory + "/" + mapLocation;

            //Write new replay to file
            XmlSerializer serializer = new XmlSerializer(toAdd.GetType());
            StreamWriter writer = new StreamWriter(mapLocation);
            serializer.Serialize(writer, toAdd);
            writer.Close();
            if(selectedMap != -1) {
                maps.RemoveAt(selectedMap);
                maps.Insert(selectedMap, newTag);
            }
            else maps.Add(newTag);
            writeToFile();

        }
        //END JONATHAN!!!

        //Get board for playing
        public Board getBoard()
        {
            Board thisBoard = null;
            Map thisMap = getMap();
            if (thisMap != null)
                thisBoard = new Board(thisMap);
            return thisBoard;
        }


        //JONATHAN (MAYBE)!!!
        //Get map for editing
        public Map getMap()
        {
            Map thisMap = new Map();
            if (selectedMap >= 0 && maps.Count != 0)
            {
                MapTag tag = maps[selectedMap];
                string mapLocation = mapDirectory + "/" + tag.fileName;
                XmlSerializer serializer = new XmlSerializer(thisMap.GetType());
                FileStream xmlFileStream = File.Open(mapLocation, FileMode.Open);
                thisMap = (Map)serializer.Deserialize(xmlFileStream);
                xmlFileStream.Close();
            }
            if (selectedMap < 0) thisMap = createMap();
            return thisMap;
        }

        public Map createMap()
        {
            return new Map();
        }
        //END JONATHAN (MAYBE)!!!
    }

    public struct MapTag
    {
        public string fileName;
        public DateTime timeCreated;
        public int width;
        public int height;
        public bool hasTerritories;
        public bool hasRaceTerritory;
        public int numPlayers;
    }

    #endregion

    /*
     * Maps cannot be finalized if:
     * 
     * they're 0 x 0
     * they have no name
     * they have no starting players
     */


    [Serializable]
    public class Map
    {
        public enum DEFAULTS
        {
            MAX_XDIMENSION = 30,
            MAX_YDIMENSION = 30
        }

        public string name;
        public Tile[][] board;
        public List<List<Point>> territories;
        public List<Point> raceTerritory;
        public List<List<Point>> startingAreas;
        public List<HashSet<Point>> reservedTerritoryPoints;
        public List<HashSet<Point>> reservedStartingPoints;

        int selectionMode;

        public Map()
        {
            name = "";
            setDimensions(10, 10);
            territories = new List<List<Point>>();
            raceTerritory = new List<Point>();
            startingAreas = new List<List<Point>>();

            reservedTerritoryPoints = new List<HashSet<Point>>();
            reservedStartingPoints = new List<HashSet<Point>>();
            selectionMode = 0;

            //for testing
            /*         name = "untitled";
                     addStartingArea(getSelectedGroup(new Point(0, 0), new Point(5, 5)));
                     addStartingArea(getSelectedGroup(new Point(20, 20), new Point(24, 24)));
                     */
        }

        #region getters

        //JONATHAN!!!
        public bool isInGroup(Tile tile)
        {
            bool isInGroup = false;
            switch (selectionMode)
            {
                case (int)SELECTION_MODES.TERRITORIES:
                    foreach (HashSet<Point> territory in reservedTerritoryPoints)
                    {
                        if (territory.Contains(tile.getLocation()))
                        {
                            isInGroup = true;
                            break;
                        }
                    }
                    break;
                case (int)SELECTION_MODES.RACE_TERRITORY:
                    if (raceTerritory.Contains(tile.getLocation()))
                        isInGroup = true;
                    break;
                case (int)SELECTION_MODES.STARTING_AREAS:
                    foreach (HashSet<Point> startingArea in reservedStartingPoints)
                    {
                        if (startingArea.Contains(tile.getLocation()))
                        {
                            isInGroup = true;
                            break;
                        }
                    }
                    break;
            }
            return isInGroup;
        }

        //Gives color of starting area for view
        public int getStartingAreaPlayer(Tile tile)
        {
            int player = -1;
            for (int i = 0; i < reservedStartingPoints.Count; i++)
            {
                if(reservedStartingPoints[i].Contains(tile.getLocation())) {
                    player = i;
                }
            }
            return player;
        }
        //END JONATHAN!!!!
        #endregion

        //JONATHAN!!
        public bool setName(string newName)
        {
            bool successful = true;
            if (name != null) name = newName;
            else successful = false;
            return successful;
        }

        public bool setDimensions(int x, int y)
        {
            bool successful = true;
            Tile[][] oldBoard = board;
            if (x > 0 & y > 0 && x < (int)DEFAULTS.MAX_XDIMENSION &&
                                y < (int)DEFAULTS.MAX_YDIMENSION)
            {
                board = new Tile[x][];
                for (int i = 0; i < x; i++)
                {
                    board[i] = new Tile[y];
                    for (int j = 0; j < y; j++)
                    {
                        board[i][j] = new Tile(new Point(i,j));
                       /* if (oldBoard != null && i < oldBoard.Length && j < oldBoard[0].Length)
                        {
                            board[i][j] = oldBoard[i][j];
                        }*/
                    }
                }
            }
            else successful = false;
            return successful;
        }
        //END JONATHAN!!!!

        private bool isBrokenLink(Point point)
        {
            return board[point.X][point.Y] is BrokenLinkTile;
        }

        int currentTileType;

        public/*private*/ void setTileType(int type)
        {
            currentTileType = type;
        }

        #region group selection

        //Jonathan!!!!
        //may need to move enum out of class
        public enum SELECTION_MODES
        {
            TILES,
            TILE_ROTATE,
            TERRITORIES,
            RACE_TERRITORY,
            STARTING_AREAS,
        }
        
        public void changeSelectionMode(int mode)
        {
            selectionMode = mode;
        }

        public bool applyToGroup(List<Tile> tiles)
        {
            bool successful = true;
            HashSet<Point> selected = getSelectedGroup(tiles);
            switch (selectionMode)
            {
                case (int)SELECTION_MODES.TILES:
                    setTiles(selected);
                    break;
                case (int)SELECTION_MODES.TILE_ROTATE:
                    rotateTiles(selected);
                    break;
                case (int)SELECTION_MODES.TERRITORIES:
                    successful = addTerritory(selected);
                    if (!successful) successful = removeTerritory(selected);
                    break;
                case (int)SELECTION_MODES.RACE_TERRITORY:
                    successful = addRaceTerritory(selected);
                    break;
                case (int)SELECTION_MODES.STARTING_AREAS:
                    successful = addStartingArea(selected);
                    if (!successful) successful = removeStartingArea(selected);
                    break;
            }
            return successful;
        }
        //END JONATHAN!!!!!!

        private void rotateTiles(HashSet<Point> toSet)
        {
            foreach (Point point in toSet)
            {
                board[point.X][point.Y].rotateTile();
            }
        }

        private void setTiles(HashSet<Point> toSet)
        {
            foreach (Point point in toSet)
            {
                Tile newT;
                switch (currentTileType)
                {
                    case (int)TILE_VARIANTS.HIGH_BANDWIDTH: newT = new HighBandwidthTile(point); break;
                    case (int)TILE_VARIANTS.LOW_BANDWIDTH: newT = new LowBandwidthTile(point); break;
                    case (int)TILE_VARIANTS.WIFI: newT = new WiFiTile(point); break;
                    case (int)TILE_VARIANTS.BROKEN_LINK: newT = new BrokenLinkTile(point); break;
                    case (int)TILE_VARIANTS.BUS: newT = new BusTile(point, (int)DIRECTIONS.LEFT); break;
                    case (int)TILE_VARIANTS.DYING_LINK: newT = new DyingLinkTile(point); break;
                    case (int)TILE_VARIANTS.FIREWALL: newT = new FirewallTile(point, (int)DIRECTIONS.LEFT); break;
                    
                    default: newT = new Tile(point); break;
                }
                board[point.X][point.Y] = newT;
            }
        }

        private bool addTerritory(HashSet<Point> newTerritory)
        {
            newTerritory.RemoveWhere(isBrokenLink);
            bool successful = true;
            foreach (HashSet<Point> pastTerritory in reservedTerritoryPoints)
            {
                if (pastTerritory.Overlaps(newTerritory))
                {
                    successful = false;
                    break;
                }
            }
            if (newTerritory.Count == 0) successful = false;
            if (successful)
            {
                reservedTerritoryPoints.Add(newTerritory);
                territories.Add(new List<Point>(newTerritory));
            }
            return successful;
        }
        private bool removeTerritory(HashSet<Point> removal)
        {
            bool successful = true;
            int i;
            for (i = 0; i < territories.Count &&
                        reservedTerritoryPoints[i].Overlaps(removal); i++) ;
            if (i < territories.Count)
            {
                reservedTerritoryPoints.RemoveAt(i);
                territories.RemoveAt(i);
            }
            else successful = false;
            return successful;
        }

        private bool addRaceTerritory(HashSet<Point> newTerritory)
        {
            bool successful = true;
            newTerritory.RemoveWhere(isBrokenLink);
            if (newTerritory.Count > 0)
                raceTerritory = new List<Point>(newTerritory);
            else successful = false;

            return successful;
        }

        private bool addStartingArea(HashSet<Point> newStartingArea)
        {
            newStartingArea.RemoveWhere(isBrokenLink);

            bool successful = true;
            int i = 0;
            foreach (HashSet<Point> pastStartingPoint in reservedStartingPoints)
            {
                if (pastStartingPoint.Overlaps(newStartingArea))
                {
                    successful = false;
                    break;
                }
            }
            if (newStartingArea.Count == 0) successful = false;
            if (successful)
            {
                reservedStartingPoints.Add(newStartingArea);
                startingAreas.Add(new List<Point>(newStartingArea));
            }
            return successful;
        }
        private bool removeStartingArea(HashSet<Point> removal)
        {
            bool successful = true;
            int i;
            for (i = 0; i < startingAreas.Count &&
                        reservedStartingPoints[i].Overlaps(removal); i++) ;
            if (i < startingAreas.Count)
            {
                reservedStartingPoints.RemoveAt(i);
                startingAreas.RemoveAt(i);
            }
            else successful = false;
            return successful;
        }

        //to be used in display
        private HashSet<Point> getSelectedGroup(List<Tile> tiles)
        {
            HashSet<Point> selected = new HashSet<Point>();
            foreach (Tile t in tiles)
            {
                selected.Add(t.getLocation());
            }
            return selected;
        }

        #endregion
        //IF FINALIZABLE....
        //IF NOT... don't perform the "action" yet (of saving it)
        public bool canFinalize()
        {
            bool successful = startingAreas.Count > 1;
            int width = board.Length;
            if (!(width > 0 && width <= (int)DEFAULTS.MAX_XDIMENSION &&
                board[0].Length > 0 && board[0].Length <= (int)DEFAULTS.MAX_YDIMENSION))
            {
                successful = false;
            }
            if (successful)
                selectionMode = 0;
            return successful;
        }
    }
}
